package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author fenge
 */

@Aspect
@Component
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RBloomFilter bloomFilter;

    /**
     * 1.必须返回Object
     * 2.必须要有ProceedingJoinPoint参数
     * 3.必须抛出Throwable类型的异常
     * 4.必须手动执行目标方法:joinPoint.proceed()
     *
     * 通知方法:
     *  1.获取目标方法参数:joinPoint.getArgs()
     *  2.
     *
     * @return
     */
//    @Around("execution(* com.atguigu.gmall.index.service.*.*(..))")
    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object test(ProceedingJoinPoint joinPoint) throws Throwable{
        // 获取方法签名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        // 获取方法对象上的指定注解
        GmallCache gmallCache = signature.getMethod().getAnnotation(GmallCache.class);
        // 获取注解中的前缀
        String prefix = gmallCache.prefix();

        // 方法的形参列表,这里以逗号分隔,组装成字符串
        String args = StringUtils.join(joinPoint.getArgs(), ",");
        String key = prefix + args;

        // 为了防止缓存穿透,使用布隆过滤器
        if (!bloomFilter.contains(key)){
            return null;
        }

        // 1.先查询缓存,命中则直接返回
        String json = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)){
            return JSON.parseObject(json,signature.getReturnType());
        }
        // 2.防止缓存击穿,添加分布式锁
        String lock_prefix = gmallCache.lock();
        RLock lock = redissonClient.getLock(lock_prefix + args);
        lock.lock();

        try {
            // 3.再查缓存,命中则直接返回
            String json2 = redisTemplate.opsForValue().get(prefix + args);
            if (StringUtils.isNotBlank(json2)){
                return JSON.parseObject(json2,signature.getReturnType());
            }

            // 4.执行目标方法
            System.out.println("前增强");
            Object result = joinPoint.proceed(joinPoint.getArgs());
            System.out.println("后增强");

            // 5.放入缓存,并释放分布式锁
            int timeout = gmallCache.timeout()+new Random().nextInt(gmallCache.random());
            redisTemplate.opsForValue().set(key,JSON.toJSONString(result),timeout, TimeUnit.MINUTES);

            return result;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 20, 0.3);
        for (Integer i = 0; i < 30; i++) {
            bloomFilter.put(Integer.valueOf(i).toString());
        }
        for (Integer i = 0; i < 50; i++) {
            System.out.println(i+":"+bloomFilter.mightContain(Integer.valueOf(i).toString()));
        }
    }
}
